<?php

namespace Yolo\Commands;

use Hyperf\Command\Annotation\Command;
use Hyperf\Command\Command as HyperfCommand;
use PharData;
use Psr\Container\ContainerInterface;
use Symfony\Component\Filesystem\Filesystem;
use Yolo\Di\DI;
use Yolo\Packages\PackageManager;
use Yolo\Service\ApplicationContainer;
use Yolo\Utils\Network;

#[Command]
class InstallPackage extends HyperfCommand
{
    private ApplicationContainer $applicationContainer;

    private array $packageServer = [];

    private PackageManager $packageManager;

    private array $installingPackages = [];

    public function __construct(protected ContainerInterface $container)
    {
        parent::__construct('package:install');

        $this->applicationContainer = DI::use(ApplicationContainer::class);
        $this->packageManager = DI::use(PackageManager::class);
    }

    public function configure(): void
    {
        parent::configure();
        $this->setDescription('Get a package information.');
        $this->addArgument('package', null, 'Package name.');
        $this->addArgument('version', null, 'Package version, default latest version.');
    }

    public function handle(): void
    {
        $package = $this->input->getArgument('package') ?? '';
        $version = $this->input->getArgument('version') ?? '';

        $this->packageServer = $this->applicationContainer->getConfig()->get('packageServer');
        if (!$this->packageServer || !$this->packageServer['host']) {
            throw new \Exception('Package server is not configured.');
        }


        if ($package) {
            $shouldInstallPackages[$package] = $version;
        } else {
            $shouldInstallPackages = $this->packageManager->getDependencies();
        }

        // 备份yolo-packages.json文件
        $this->packageManager->backupPackageFile();

        try {

            foreach ($shouldInstallPackages as $packageName => $version) {

                $this->installPackage($packageName, $version);

                $this->packageManager->updatePackageLockDependency($packageName, [
                    'version' => $version,
                    'service' => $this->installingPackages[$packageName]['serviceName'],
                    'path' => $this->installingPackages[$packageName]['installPath'],
                ]);
            }

        } catch (\Exception $e) {

            $this->error($e->getMessage());

            $this->warn("Rollback yolo-packages.json file...");
            $this->packageManager->restorePackageFile();
            return;
        }

        $this->packageManager->savePackageFile();
        $this->packageManager->savePackageLockFile();

        $this->info("Packages installed successfully.");
    }

    private function installPackage(string $package, string $version)
    {
        $filesystem = new Filesystem();

        $info = $this->packageManager->getPackageInfo($package, $version, true);

        $this->packageManager->updateDependency($info['name'], $info['version']);

        $installPath = BASE_PATH . "/app/Package/{$info['context']['serviceName']}";
        if (!is_dir($installPath)) {
            mkdir($installPath, 0755, true);
        }

        $tmpFileName = md5($info['file']) . '.tar';
        $tmpFile = BASE_PATH . '/app/Package/' . $tmpFileName;
        $this->installingPackages[$package] = [
            'status' => 'downloading',
            'serviceName' => $info['context']['serviceName'],
            'version' => $info['version'],
            'tmpFile' => $tmpFile,
            'installPath' => $installPath,
        ];

        $this->info("Downloading {$info['name']} {$info['version']}...");
        Network::downloadFile($this->packageServer['host'] . $info['file'],  $tmpFile);

        $filesystem->remove($installPath);
        $filesystem->mkdir($installPath);

        $this->installingPackages[$package]['status'] = 'downloaded';

        $this->info("Installing {$info['name']} {$info['version']}...");
        $this->installingPackages[$package]['status'] = 'installing';

        $this->unzip($tmpFile, $installPath);

        $filesystem->remove($tmpFile);
    }

    private function unzip(string $file, string $extractTo)
    {
        $phar = new PharData($file);

        $phar->extractTo($extractTo);
    }
}
